淺談處置模式模式和 using 陳述式
TLDR
- 非託管資源(如資料庫連線、檔案)必須手動釋放,垃圾回收器無法自動處理。
- 實作
IDisposable介面並在Dispose()方法中釋放資源是標準做法。 - 實作
IDisposable時應優先使用Dispose,並透過GC.SuppressFinalize(this)阻止 GC 重複呼叫解構式。 - 若物件同時實作
IDisposable與IAsyncDisposable,建議兩者皆實作以確保相容性,且在 ASP.NET Core 中DisposeAsync()具有優先權。 using陳述式本質上是try...finally的語法糖,能確保資源在離開作用域時被正確釋放。- C# 8.0 引入了更簡潔的
using宣告語法,無需額外的巢狀大括弧即可在作用域結束時自動釋放。
處置模式 (Dispose Pattern)
非託管資源的處理
什麼情況下會遇到這個問題:當程式需要操作資料庫連線、檔案存取或作業系統原生控制代碼 (Handle) 等非託管資源時。
由於 .NET 的垃圾回收器 (GC) 僅管理託管記憶體,無法自動回收非託管資源,開發者必須透過以下方式手動釋放:
- 實作
IDisposable介面:在Dispose()方法中釋放資源。 - 宣告解構式 (Finalizer):作為最後一道防線,在 GC 回收物件時自動呼叫。
實作範例
以下為標準的處置模式實作方式,包含防止重複釋放的機制:
csharp
public class ResourceHandle : IDisposable {
private bool disposed = false;
private IntPtr unmanagedResource;
private ManagedResource managedResource;
public ResourceHandle() {
managedResource = new ManagedResource();
}
public void Dispose() {
Dispose(true);
// 告知 GC 此物件已手動釋放,無需再呼叫解構式
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing) {
if (!disposed) {
if (disposing) {
managedResource?.Dispose();
managedResource = null;
}
// 釋放非託管資源
if (unmanagedResource != IntPtr.Zero) {
FreeUnmanagedResource(unmanagedResource);
unmanagedResource = IntPtr.Zero;
}
disposed = true;
}
}
~ResourceHandle() {
Dispose(false);
}
}非同步處置模式
什麼情況下會遇到這個問題:當資源的釋放過程涉及非同步 I/O 操作(如非同步寫入日誌或關閉網路連線)時。
針對 IAsyncDisposable 的實作建議:
- 同時實作
IDisposable與IAsyncDisposable:確保在不支援非同步釋放的舊有程式碼中,資源仍能被正確釋放。 DisposeAsync()優先:在 ASP.NET Core 的依賴注入容器中,若物件同時實作兩者,會優先呼叫DisposeAsync()。- 避免死結:在
Dispose(bool disposing)中不應呼叫非同步方法,僅處理同步釋放邏輯。
using 陳述式
確保資源釋放
什麼情況下會遇到這個問題:當需要確保物件在發生例外 (Exception) 時也能正確釋放資源,避免記憶體洩漏或連線佔用。
using 陳述式會被編譯器轉換為 try...finally 結構,確保 Dispose() 方法一定會被執行。
csharp
// 傳統 using 寫法
using (ResourceHandle handle = new ResourceHandle()) {
// 執行業務邏輯
}
// C# 8.0 簡潔寫法
{
using ResourceHandle handle = new ResourceHandle();
// 離開作用域時自動呼叫 Dispose()
}巢狀與非同步處理
對於多個資源的釋放,可簡化巢狀結構:
csharp
// 合併宣告
using (ResourceHandle h1 = new ResourceHandle(), h2 = new ResourceHandle()) {
// 執行邏輯
}
// 非同步釋放
await using (AsyncDisposableObject resource = new AsyncDisposableObject()) {
// 執行非同步邏輯
}異動歷程
- 2024-08-08 初版文件建立。
